// struct sockaddr_un的头文件为<sys/un.h>
#include <sys/un.h>
#include <stddef.h>
#include "wrap.h"

#define serv_sock "serv.socket"

/* 这是一个进程间通信的套接字服务器端*/
void domain_socket_server(){
	int ret, i, len, lfd, cfd;
	char buf[4096];
	struct sockaddr_un serv_addr, clit_addr;// 此时本地套接字需要使用sockaddr_un结构体

	pid_t pid;

	// 1. 首先需要创建套接字,本地套接字中需要使用AF_UNIX，而不是AF_INET;且SOCK_STREAM和SOCK_DGRAM均可以使用
	// 如果使用SOCK_STREAM,则可以使用listen函数设置监听上限，如果使用SOCK_DGRAM，则不可以使用listen函数设置监听上限，
	// 因为DG_RAM为报式协议，UDP的，不需要建立连接
	lfd = Socket(AF_UNIX, SOCK_STREAM, 0);

	// 2. 初始化serv_addr,并进行bind绑定
	/*struct sockaddr_un结构体中一共包含两个变量：sun_family和sun_path*/
	bzero(&serv_addr, sizeof(serv_addr));
	serv_addr.sun_family = AF_UNIX;			// 此时sin_family-->sun_family
	strcpy(serv_addr.sun_path, serv_sock);	// 绑定socket文件（此时会创建一个用于通信的伪文件）

	// 3. 进行bind绑定之前，需要计算serv_addr的长度，因为会创建一个socket文件，文件长度大小不固定，此时需要计算
	// 此时offsetof计算的是偏移量，因此sun_family的长度为sun_path-0
	len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);

	// 4. 进行bind函数绑定之前，需要清楚之前的socket文件
	unlink(serv_sock);		// 如果不适用unlink进行删除，则如果当前目录下存在该文件，bind函数会绑定失败
	ret = Bind(lfd, (struct sockaddr*)&serv_addr, len);	// note：第三个参数不可以是sizeof(servaddr)
	
	// 5. 使用listen函数设置监听上限
	Listen(lfd, 20);

	printf("Accepting....\n");

	// 6. 绑定成功后，此时进行IPC通信
	while(1){
		
		// 7. 使用Accept函数接收客户端通信
		len = sizeof(clit_addr);	// AF_UNIX + 108B
		cfd = Accept(lfd, (struct sockaddr*)&clit_addr, (socklen_t *)&len);	
		
		// 7.2 打印文件名
		len -= offsetof(struct sockaddr_un, sun_path);		// 计算文件名的长度，需要取出sun_family部分
		clit_addr.sun_path[len] = '\0';						// 确保打印时，没有乱码出现

		printf("client bind filename %s\n", clit_addr.sun_path);

		pid = fork();
		if(pid == 0){
			close(lfd);

			// 8. 进行函数操作
			while((ret = read(cfd, buf, sizeof(buf))) > 0){
				printf("client data: %s\n", buf);
				for(i=0; i<ret; ++i){
					buf[i] = toupper(buf[i]);
				}
				// 将数据写回至客户端
				write(cfd, buf, ret);
			}
			close(cfd);
		}else{
			close(cfd);
		}

	}

	close(lfd);

}


int main(int argc, char *argv[]){
	domain_socket_server();
}
